/* SexpFormatterCallback.cs -- 
   Copyright (C) 2008  Casey Marshall.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


using System.Collections;
using System.Runtime.Serialization;
using SexpSharp;

namespace SexpSharp.Serialization
{
    internal class SexpFormatterCallback : ParseCallback
    {
        enum State
        {
            ObjectBegin,
            Type,
            ObjectValue,
            FieldName,
            ArrayBegin,
            ArrayValue,
            ArrayOfValue,
            PrimValue
        }

        private State state;
        private Stack objectStack;
        private object current;
        private Type currentType;
        private MemoryStream atomBuffer;
        private int arrayof_Pos;
        private int arrayof_Shift;

        internal SexpFormatterCallback()
        {
            state = State.ObjectBegin;
            atomBuffer = new MemoryStream();
        }

        public void OnListBegin()
        {
            switch (state)
            {
            case State.ObjectBegin:
                state = Type;
                break;

            case State.ObjectValue:
                state = Type;
                break;

            case State.ArrayBegin:
                state = State.ArrayValue;
                break;
                
            default:
                throw new SerializationException("unexpected list begin");
            }
        }

        public void OnListEnd()
        {
            
        }

        public void OnAtomBegin(int atomLength)
        {
            switch (state)
            {
            case State.Type:
                atomBuffer.SetLength(0);
                atomBuffer.Capacity = atomLength;
                break;

            case State.FieldName:
                atomBuffer.SetLength(0);
                atomBuffer.Capacity = atomLength;
                break;

            case State.PrimValue:
                atomBuffer.SetLength(0);
                atomBuffer.Capacity = atomLength;
                break;

            case State.ArrayOfValue:
                if (currentType == typeof(Boolean))
                {
                    current = new bool[atomLength];
                    arrayof_Shift = 0;
                }
                else if (currentType == typeof(Byte))
                {
                    current = new byte[atomLength];
                    arrayof_Shift = 0;
                }
                else if (currentType == typeof(SByte))
                {
                    current = new sbyte[atomLength];
                    arrayof_Shift = 0;
                }
                else if (currentType == typeof(UInt16))
                {
                    if ((atomLength % 2) != 0)
                        throw new SerializationException("arrayof ushort must be a multiple of 2 bytes");
                    current = new ushort[atomLength / 2];
                    arrayof_Shift = 8;
                }
                else if (currentType == typeof(Int16))
                {
                    if ((atomLength % 2) != 0)
                        throw new SerializationException("arrayof short must be a multiple of 2 bytes");
                    current = new short[atomLength / 2];
                    arrayof_Shift = 8;
                }
                else if (currentType == typeof(UInt32))
                {
                    if ((atomLength % 4) != 0)
                        throw new SerializationException("arrayof uint must be a multiple of 4 bytes");
                    current = new uint[atomLength / 4];
                    arrayof_Shift = 24;
                }
                else if (currentType == typeof(Int32))
                {
                    if ((atomLength % 4) != 0)
                        throw new SerializationException("arrayof int must be a multiple of 4 bytes");
                    current = new int[atomLength / 4];
                    arrayof_Shift = 24;
                }
                else if (currentType == typeof(UInt64))
                {
                    if ((atomLength % 8) != 0)
                        throw new SerializationException("arrayof ulong must be a multiple of 8 bytes");
                    current = new uint[atomLength / 8];
                    arrayof_Shift = 56;
                }
                else if (currentType == typeof(Int64))
                {
                    if ((atomLength % 8) != 0)
                        throw new SerializationException("arrayof long must be a multiple of 8 bytes");
                    current = new int[atomLength / 8];
                    arrayof_Shift = 56;
                }
                else
                    throw new SerializationException("unsupported arrayof type: " + currentType);
                arrayof_Pos = 0;
                break;

            default:
                throw new SerializationException("not expecting an atom");
            }
        }

        public void OnAtomData(byte[] input, int offset, int length)
        {
            switch (state)
            {
            case State.Type:
                atomBuffer.Write(input, offset, length);
                break;

            case State.FieldName:
                atomBuffer.Write(input, offset, length);
                break;

            case State.PrimValue:
                atomBuffer.Write(input, offset, length);
                break;

            case State.ArrayofValue:
                if (currentType == typeof(Boolean))
                {
                    for (int i = offset; i < offset+length; i++)
                        ((bool[]) current)[arrayof_Pos++] = input[i] != 0;
                }
                else if (currentType == typeof(Byte))
                {
                    Buffer.BlockCopy(input, offset, current, arrayof_Pos, length);
                    arrayof_Pos += length;
                }
                else if (currentType == typeof(SByte))
                {
                    for (int i = offset; i < offset+length; i++)
                        ((sbyte[]) current)[arrayof_Pos++] = unchecked((sbyte) input[i]);
                }
                // FIXME, this should be simpler
                else if (currentType == typeof(UInt16))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((ushort[]) current)[arrayof_Pos] = (ushort) ((input[i] & 0xFF) << arrayof_Shift);
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                else if (currentType == typeof(Int16))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((short[]) current)[arrayof_Pos]
                            = unchecked((short) ((input[i] & 0xFF) << arrayof_Shift));
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                else if (currentType == typeof(UInt32))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((uint[]) current)[arrayof_Pos] = (uint) ((input[i] & 0xFF) << arrayof_Shift);
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                else if (currentType == typeof(Int32))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((int[]) current)[arrayof_Pos]
                            = unchecked((int) ((int[i] & 0xFF) << arrayof_Shift));
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                else if (currentType == typeof(UInt64))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((ulong[]) current)[arrayof_Pos] = (ulong) (input[i] & 0xFF) << arrayof_Shift;
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                else if (currentType == typeof(Int64))
                {
                    for (int i = offset; i < offset+length; i++)
                    {
                        ((long[]) current)[arrayof_Pos]
                            = unchecked((long) ((ulong) (int[i] & 0xFF) << arrayof_Shift));
                        arrayof_Shift -= 8;
                        if (arrayof_Shift < 0)
                        {
                            arrayof_Shift = 8;
                            arrayof_Pos++;
                        }
                    }
                }
                break;
            }
        }

        public void OnAtomEnd()
        {
            switch (state)
            {
            case State.ArrayofValue:
                if (arrayof_Pos != ((Array) current).Length)
                    throw new SerializationException("arrayof is short some elements");
                if (arrayof_Shift != 0)
                    throw new SerializationException("missing bytes in arrayof");
                break;

            default:
                throw new SerializationException("unexpected end of atom state: " + state);
            }
        }

        public void OnAtom(byte[] atomBytes, int offset, int length)
        {
            OnAtomBegin(length);
            OnAtomBytes(atomBytes, offset, length);
            OnAtomEnd();
        }
        
        public void OnEndOfInput()
        {
        }
    }
}